using System;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace Com.FirstSolver.Splash;

public abstract class SymmetricAlgorithm : IDisposable
{
	public readonly CipherDirection mDirection;

	public readonly CipherMode mMode;

	public readonly PaddingMode mPadding;

	public readonly int mBlockSizeInBytes = 16;

	protected byte[] mInnerIV = null;

	protected IVectorProvider mVectorGenerator = null;

	protected readonly byte[] mInputBuffer;

	protected readonly byte[] mOutputBuffer;

	private readonly byte[] mLiveIV = null;

	protected bool mIsReady = false;

	protected bool mIsHomeBlock = true;

	protected int mRemainderBytes = 0;

	protected uint mOrdinal = 0u;

	private bool disposed = false;

	public abstract string AlgorithmName { get; }

	public abstract int mKeySize { get; }

	public abstract int mKeySizeInBytes { get; }

	public abstract byte[] mKey { get; }

	public byte[] mIV => (mInnerIV == null) ? null : ((byte[])mInnerIV.Clone());

	public SymmetricAlgorithm(CipherDirection direction = CipherDirection.Encryption, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, int blockSize = 16)
	{
		mDirection = direction;
		mMode = mode;
		mPadding = padding;
		mBlockSizeInBytes = blockSize;
		mInputBuffer = new byte[blockSize << 1];
		mOutputBuffer = new byte[blockSize << 1];
		if (mMode != 0 && mMode != CipherMode.ECBCTS)
		{
			mInnerIV = new byte[blockSize];
			mLiveIV = new byte[blockSize];
		}
	}

	public virtual bool Initialize(byte[] key, byte[] iv = null)
	{
		if (mMode == CipherMode.CTR)
		{
			return false;
		}
		if (mMode != 0 && mMode != CipherMode.ECBCTS)
		{
			if (iv == null || iv.Length != mBlockSizeInBytes)
			{
				return false;
			}
			iv.CopyTo(mInnerIV, 0);
		}
		if (!((mDirection.Equals(CipherDirection.Encryption) || mMode.Equals(CipherMode.CFB) || mMode.Equals(CipherMode.OFB)) ? SetEncryptKey(key) : SetDecryptKey(key)))
		{
			return false;
		}
		mIsHomeBlock = true;
		mIsReady = true;
		return true;
	}

	public virtual bool Initialize(byte[] key, IVectorProvider vectorGenerator)
	{
		if (mMode != CipherMode.CTR || vectorGenerator == null)
		{
			return false;
		}
		if (!SetEncryptKey(key))
		{
			return false;
		}
		mVectorGenerator = vectorGenerator;
		mIsHomeBlock = true;
		mIsReady = true;
		return true;
	}

	protected abstract bool SetEncryptKey(byte[] key);

	protected abstract bool SetDecryptKey(byte[] key);

	protected virtual void Transform(byte[] input, int offset, byte[] output)
	{
		switch (mMode)
		{
		case CipherMode.ECB:
		case CipherMode.ECBCTS:
			TransformCore(mDirection, input, offset, output);
			break;
		case CipherMode.CBC:
		case CipherMode.CBCCTS:
			if (mDirection.Equals(CipherDirection.Encryption))
			{
				for (int j = 0; j < mBlockSizeInBytes; j++)
				{
					mLiveIV[j] ^= input[offset++];
				}
				TransformCore(CipherDirection.Encryption, mLiveIV, 0, output);
				Array.Copy(output, mLiveIV, mBlockSizeInBytes);
			}
			else
			{
				TransformCore(CipherDirection.Decryption, input, offset, output);
				for (int l = 0; l < mBlockSizeInBytes; l++)
				{
					output[l] ^= mLiveIV[l];
				}
				Array.Copy(input, offset, mLiveIV, 0, mBlockSizeInBytes);
			}
			break;
		case CipherMode.CFB:
			TransformCore(CipherDirection.Encryption, mLiveIV, 0, output);
			if (mDirection.Equals(CipherDirection.Encryption))
			{
				for (int k = 0; k < mBlockSizeInBytes; k++)
				{
					output[k] ^= input[offset++];
				}
				Array.Copy(output, mLiveIV, mBlockSizeInBytes);
			}
			else
			{
				Array.Copy(input, offset, mLiveIV, 0, mBlockSizeInBytes);
				for (int i = 0; i < mBlockSizeInBytes; i++)
				{
					output[i] ^= input[offset++];
				}
			}
			break;
		case CipherMode.OFB:
		{
			TransformCore(CipherDirection.Encryption, mLiveIV, 0, mLiveIV);
			for (int m = 0; m < mBlockSizeInBytes; m++)
			{
				output[m] = (byte)(input[offset++] ^ mLiveIV[m]);
			}
			break;
		}
		case CipherMode.PCBC:
			if (mDirection.Equals(CipherDirection.Encryption))
			{
				for (int i4 = 0; i4 < mBlockSizeInBytes; i4++)
				{
					mLiveIV[i4] ^= input[offset++];
				}
				TransformCore(CipherDirection.Encryption, mLiveIV, 0, output);
				offset -= mBlockSizeInBytes;
				for (int i5 = 0; i5 < mBlockSizeInBytes; i5++)
				{
					mLiveIV[i5] = (byte)(input[offset++] ^ output[i5]);
				}
			}
			else
			{
				TransformCore(CipherDirection.Decryption, input, offset, output);
				for (int n = 0; n < mBlockSizeInBytes; n++)
				{
					output[n] ^= mLiveIV[n];
				}
				for (int i3 = 0; i3 < mBlockSizeInBytes; i3++)
				{
					mLiveIV[i3] = (byte)(input[offset++] ^ output[i3]);
				}
			}
			break;
		case CipherMode.CTR:
		{
			mVectorGenerator.GetVector(mOrdinal++, mLiveIV);
			TransformCore(CipherDirection.Encryption, mLiveIV, 0, output);
			for (int i2 = 0; i2 < mBlockSizeInBytes; i2++)
			{
				output[i2] ^= input[offset++];
			}
			break;
		}
		}
	}

	protected abstract void TransformCore(CipherDirection direction, byte[] input, int offset, byte[] output);

	protected virtual void TransformTail()
	{
		switch (mMode)
		{
		case CipherMode.CFB:
		case CipherMode.OFB:
		case CipherMode.CTR:
		{
			if (mMode.Equals(CipherMode.CTR))
			{
				mVectorGenerator.GetVector(mOrdinal, mLiveIV);
			}
			TransformCore(CipherDirection.Encryption, mLiveIV, 0, mOutputBuffer);
			for (int i = 0; i < mRemainderBytes; i++)
			{
				mOutputBuffer[i] ^= mInputBuffer[i];
			}
			break;
		}
		case CipherMode.ECBCTS:
		{
			int M = mRemainderBytes - mBlockSizeInBytes;
			TransformCore(mDirection, mInputBuffer, 0, mOutputBuffer);
			Array.Copy(mOutputBuffer, 0, mOutputBuffer, mBlockSizeInBytes, M);
			Array.Copy(mInputBuffer, mBlockSizeInBytes, mOutputBuffer, 0, M);
			TransformCore(mDirection, mOutputBuffer, 0, mOutputBuffer);
			break;
		}
		case CipherMode.CBCCTS:
		{
			int M2 = mRemainderBytes - mBlockSizeInBytes;
			if (mDirection.Equals(CipherDirection.Encryption))
			{
				for (int l = 0; l < mBlockSizeInBytes; l++)
				{
					mLiveIV[l] ^= mInputBuffer[l];
				}
				TransformCore(CipherDirection.Encryption, mLiveIV, 0, mOutputBuffer);
				Array.Copy(mOutputBuffer, 0, mOutputBuffer, mBlockSizeInBytes, M2);
				Array.Copy(mInputBuffer, mBlockSizeInBytes, mLiveIV, 0, M2);
				Array.Clear(mLiveIV, M2, mBlockSizeInBytes - M2);
				for (int j = 0; j < mBlockSizeInBytes; j++)
				{
					mOutputBuffer[j] ^= mLiveIV[j];
				}
				TransformCore(CipherDirection.Encryption, mOutputBuffer, 0, mOutputBuffer);
			}
			else
			{
				TransformCore(CipherDirection.Decryption, mInputBuffer, 0, mOutputBuffer);
				int offset = mBlockSizeInBytes;
				for (int k = 0; k < M2; k++)
				{
					mOutputBuffer[k] ^= mInputBuffer[offset++];
				}
				Array.Copy(mOutputBuffer, 0, mOutputBuffer, mBlockSizeInBytes, M2);
				Array.Copy(mInputBuffer, mBlockSizeInBytes, mOutputBuffer, 0, M2);
				TransformCore(CipherDirection.Decryption, mOutputBuffer, 0, mOutputBuffer);
				for (int m = 0; m < mBlockSizeInBytes; m++)
				{
					mOutputBuffer[m] ^= mLiveIV[m];
				}
			}
			break;
		}
		}
	}

	public virtual void TransformBlock(byte[] input, int offset, int count, IList<byte> output)
	{
		if (!mIsReady)
		{
			throw new UnauthorizedAccessException();
		}
		if (input == null || count <= 0)
		{
			return;
		}
		if (mIsHomeBlock)
		{
			mRemainderBytes = 0;
			if (mMode.Equals(CipherMode.CTR))
			{
				mOrdinal = 0u;
			}
			else if (!mMode.Equals(CipherMode.ECB) && !mMode.Equals(CipherMode.ECBCTS))
			{
				mInnerIV.CopyTo(mLiveIV, 0);
			}
			mIsHomeBlock = false;
		}
		int FillBytes = mBlockSizeInBytes - mRemainderBytes;
		if (mRemainderBytes != 0 && ((mMode < CipherMode.ECBCTS && count >= FillBytes) || (mMode >= CipherMode.ECBCTS && count > mBlockSizeInBytes + FillBytes)))
		{
			if (FillBytes > 0)
			{
				Array.Copy(input, offset, mInputBuffer, mRemainderBytes, FillBytes);
			}
			Transform(mInputBuffer, 0, mOutputBuffer);
			for (int k = 0; k < mBlockSizeInBytes; k++)
			{
				output.Add(mOutputBuffer[k]);
			}
			if (mRemainderBytes > mBlockSizeInBytes)
			{
				mRemainderBytes -= mBlockSizeInBytes;
				if (count + mRemainderBytes > mBlockSizeInBytes << 1)
				{
					FillBytes = mBlockSizeInBytes - mRemainderBytes;
					Array.Copy(input, offset, mInputBuffer, mBlockSizeInBytes + mRemainderBytes, FillBytes);
					Transform(mInputBuffer, mBlockSizeInBytes, mOutputBuffer);
					for (int j = 0; j < mBlockSizeInBytes; j++)
					{
						output.Add(mOutputBuffer[j]);
					}
				}
				else
				{
					Array.Copy(mInputBuffer, mBlockSizeInBytes, mInputBuffer, 0, mRemainderBytes);
				}
			}
			if (FillBytes >= 0)
			{
				offset += FillBytes;
				count -= FillBytes;
				mRemainderBytes = 0;
			}
		}
		while ((mMode < CipherMode.ECBCTS && count >= mBlockSizeInBytes) || (mMode >= CipherMode.ECBCTS && count > mBlockSizeInBytes << 1))
		{
			Transform(input, offset, mOutputBuffer);
			for (int i = 0; i < mBlockSizeInBytes; i++)
			{
				output.Add(mOutputBuffer[i]);
			}
			offset += mBlockSizeInBytes;
			count -= mBlockSizeInBytes;
		}
		if (count > 0)
		{
			Array.Copy(input, offset, mInputBuffer, mRemainderBytes, count);
			mRemainderBytes += count;
		}
	}

	public virtual bool TransformFinalBlock(byte[] input, int offset, int count, IList<byte> output)
	{
		if (!mIsReady)
		{
			throw new UnauthorizedAccessException();
		}
		TransformBlock(input, offset, count, output);
		bool IsOK = true;
		if (mMode.Equals(CipherMode.CFB) || mMode.Equals(CipherMode.OFB) || mMode.Equals(CipherMode.CTR) || mMode >= CipherMode.ECBCTS)
		{
			if (mMode >= CipherMode.ECBCTS && mRemainderBytes <= mBlockSizeInBytes)
			{
				IsOK = false;
			}
			else if (mRemainderBytes > 0)
			{
				TransformTail();
				for (int i = 0; i < mRemainderBytes; i++)
				{
					output.Add(mOutputBuffer[i]);
				}
			}
		}
		else if (mDirection.Equals(CipherDirection.Encryption))
		{
			int FillBytes = mBlockSizeInBytes - mRemainderBytes;
			switch (mPadding)
			{
			case PaddingMode.PKCS7:
			{
				int Index = mRemainderBytes;
				for (int i2 = 0; i2 < FillBytes; i2++)
				{
					mInputBuffer[Index++] = (byte)FillBytes;
				}
				break;
			}
			case PaddingMode.ANSIX923:
			{
				int Index4 = mRemainderBytes;
				for (int i3 = 1; i3 < FillBytes; i3++)
				{
					mInputBuffer[Index4++] = 0;
				}
				mInputBuffer[mBlockSizeInBytes - 1] = (byte)FillBytes;
				break;
			}
			case PaddingMode.ISO10126:
				mInputBuffer[mBlockSizeInBytes - 1] = (byte)FillBytes;
				break;
			case PaddingMode.ISO7816:
			{
				mInputBuffer[mRemainderBytes] = 128;
				int Index3 = mRemainderBytes + 1;
				for (int n = 1; n < FillBytes; n++)
				{
					mInputBuffer[Index3++] = 0;
				}
				break;
			}
			case PaddingMode.Zeros:
			{
				int Index2 = mRemainderBytes;
				for (int m = 0; m < FillBytes; m++)
				{
					mInputBuffer[Index2++] = 0;
				}
				break;
			}
			case PaddingMode.None:
				if (mRemainderBytes != 0)
				{
					IsOK = false;
				}
				break;
			}
			if (mPadding != PaddingMode.None)
			{
				Transform(mInputBuffer, 0, mOutputBuffer);
				for (int l = 0; l < mBlockSizeInBytes; l++)
				{
					output.Add(mOutputBuffer[l]);
				}
			}
		}
		else if (mRemainderBytes != 0)
		{
			IsOK = false;
		}
		else
		{
			switch (mPadding)
			{
			case PaddingMode.PKCS7:
			case PaddingMode.ANSIX923:
			case PaddingMode.ISO10126:
			{
				int EndIndex = output.Count - 1;
				int PaddingLen = output[EndIndex];
				if (PaddingLen >= 1 && PaddingLen <= mBlockSizeInBytes)
				{
					while (PaddingLen-- > 0)
					{
						output.RemoveAt(EndIndex--);
					}
				}
				else
				{
					IsOK = false;
				}
				break;
			}
			case PaddingMode.ISO7816:
			{
				int EndIndex2 = output.Count - 1;
				int j;
				for (j = 0; j < mBlockSizeInBytes; j++)
				{
					byte ByteValue = output[EndIndex2];
					output.RemoveAt(EndIndex2--);
					switch (ByteValue)
					{
					default:
						IsOK = false;
						break;
					case 0:
						continue;
					case 128:
						break;
					}
					break;
				}
				if (j == mBlockSizeInBytes)
				{
					IsOK = false;
				}
				break;
			}
			case PaddingMode.Zeros:
			{
				int EndIndex3 = output.Count - 1;
				int k;
				for (k = 0; k < mBlockSizeInBytes; k++)
				{
					if (output[EndIndex3] != 0)
					{
						break;
					}
					output.RemoveAt(EndIndex3--);
				}
				if (k == 0)
				{
					IsOK = false;
				}
				break;
			}
			}
		}
		mIsHomeBlock = true;
		return IsOK;
	}

	public virtual int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
	{
		if (!mIsReady)
		{
			throw new UnauthorizedAccessException();
		}
		if (inputBuffer == null || inputCount <= 0 || inputOffset < 0 || inputOffset >= inputBuffer.Length)
		{
			return 0;
		}
		if (mIsHomeBlock)
		{
			mRemainderBytes = 0;
			if (mMode.Equals(CipherMode.CTR))
			{
				mOrdinal = 0u;
			}
			else if (!mMode.Equals(CipherMode.ECB) && !mMode.Equals(CipherMode.ECBCTS))
			{
				mInnerIV.CopyTo(mLiveIV, 0);
			}
			mIsHomeBlock = false;
		}
		int nBaseOffset = outputOffset;
		int FillBytes = mBlockSizeInBytes - mRemainderBytes;
		if (mRemainderBytes != 0 && ((mMode < CipherMode.ECBCTS && inputCount >= FillBytes) || (mMode >= CipherMode.ECBCTS && inputCount > mBlockSizeInBytes + FillBytes)))
		{
			if (FillBytes > 0)
			{
				Array.Copy(inputBuffer, inputOffset, mInputBuffer, mRemainderBytes, FillBytes);
			}
			Transform(mInputBuffer, 0, mOutputBuffer);
			Array.Copy(mOutputBuffer, 0, outputBuffer, outputOffset, mBlockSizeInBytes);
			outputOffset += mBlockSizeInBytes;
			if (mRemainderBytes > mBlockSizeInBytes)
			{
				mRemainderBytes -= mBlockSizeInBytes;
				if (inputCount + mRemainderBytes > mBlockSizeInBytes << 1)
				{
					FillBytes = mBlockSizeInBytes - mRemainderBytes;
					Array.Copy(inputBuffer, inputOffset, mInputBuffer, mBlockSizeInBytes + mRemainderBytes, FillBytes);
					Transform(mInputBuffer, mBlockSizeInBytes, mOutputBuffer);
					Array.Copy(mOutputBuffer, 0, outputBuffer, outputOffset, mBlockSizeInBytes);
					outputOffset += mBlockSizeInBytes;
				}
				else
				{
					Array.Copy(mInputBuffer, mBlockSizeInBytes, mInputBuffer, 0, mRemainderBytes);
				}
			}
			if (FillBytes >= 0)
			{
				inputOffset += FillBytes;
				inputCount -= FillBytes;
				mRemainderBytes = 0;
			}
		}
		while ((mMode < CipherMode.ECBCTS && inputCount >= mBlockSizeInBytes) || (mMode >= CipherMode.ECBCTS && inputCount > mBlockSizeInBytes << 1))
		{
			Transform(inputBuffer, inputOffset, mOutputBuffer);
			Array.Copy(mOutputBuffer, 0, outputBuffer, outputOffset, mBlockSizeInBytes);
			outputOffset += mBlockSizeInBytes;
			inputOffset += mBlockSizeInBytes;
			inputCount -= mBlockSizeInBytes;
		}
		if (inputCount > 0)
		{
			Array.Copy(inputBuffer, inputOffset, mInputBuffer, mRemainderBytes, inputCount);
			mRemainderBytes += inputCount;
		}
		return outputOffset - nBaseOffset;
	}

	public virtual byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
	{
		if (!mIsReady)
		{
			throw new UnauthorizedAccessException();
		}
		if (inputBuffer == null || inputCount <= 0 || inputOffset < 0 || inputOffset >= inputBuffer.Length)
		{
			return null;
		}
		byte[] OutputBuffer = new byte[((mRemainderBytes + inputCount) / mBlockSizeInBytes + 1) * mBlockSizeInBytes];
		int OutputOffset = TransformBlock(inputBuffer, inputOffset, inputCount, OutputBuffer, 0);
		if (mMode.Equals(CipherMode.CFB) || mMode.Equals(CipherMode.OFB) || mMode.Equals(CipherMode.CTR) || mMode >= CipherMode.ECBCTS)
		{
			if (mMode >= CipherMode.ECBCTS && mRemainderBytes <= mBlockSizeInBytes)
			{
				throw new CryptographicException();
			}
			if (mRemainderBytes > 0)
			{
				TransformTail();
				Array.Copy(mOutputBuffer, 0, OutputBuffer, OutputOffset, mRemainderBytes);
				OutputOffset += mRemainderBytes;
			}
		}
		else if (mDirection.Equals(CipherDirection.Encryption))
		{
			int FillBytes = mBlockSizeInBytes - mRemainderBytes;
			switch (mPadding)
			{
			case PaddingMode.PKCS7:
			{
				int Index = mRemainderBytes;
				for (int k = 0; k < FillBytes; k++)
				{
					mInputBuffer[Index++] = (byte)FillBytes;
				}
				break;
			}
			case PaddingMode.ANSIX923:
			{
				int Index2 = mRemainderBytes;
				for (int l = 1; l < FillBytes; l++)
				{
					mInputBuffer[Index2++] = 0;
				}
				mInputBuffer[mBlockSizeInBytes - 1] = (byte)FillBytes;
				break;
			}
			case PaddingMode.ISO10126:
				mInputBuffer[mBlockSizeInBytes - 1] = (byte)FillBytes;
				break;
			case PaddingMode.ISO7816:
			{
				mInputBuffer[mRemainderBytes] = 128;
				int Index4 = mRemainderBytes + 1;
				for (int n = 1; n < FillBytes; n++)
				{
					mInputBuffer[Index4++] = 0;
				}
				break;
			}
			case PaddingMode.Zeros:
			{
				int Index3 = mRemainderBytes;
				for (int m = 0; m < FillBytes; m++)
				{
					mInputBuffer[Index3++] = 0;
				}
				break;
			}
			case PaddingMode.None:
				if (mRemainderBytes != 0)
				{
					throw new CryptographicException();
				}
				break;
			}
			if (mPadding != PaddingMode.None)
			{
				Transform(mInputBuffer, 0, mOutputBuffer);
				Array.Copy(mOutputBuffer, 0, OutputBuffer, OutputOffset, mBlockSizeInBytes);
				OutputOffset += mBlockSizeInBytes;
			}
		}
		else
		{
			if (mRemainderBytes != 0)
			{
				throw new CryptographicException();
			}
			switch (mPadding)
			{
			case PaddingMode.PKCS7:
			case PaddingMode.ANSIX923:
			case PaddingMode.ISO10126:
			{
				int PaddingLen = OutputBuffer[OutputOffset - 1];
				if (PaddingLen >= 1 && PaddingLen <= mBlockSizeInBytes)
				{
					OutputOffset -= PaddingLen;
					break;
				}
				throw new CryptographicException();
			}
			case PaddingMode.ISO7816:
			{
				int i;
				for (i = 0; i < mBlockSizeInBytes; i++)
				{
					switch (OutputBuffer[--OutputOffset])
					{
					default:
						throw new CryptographicException();
					case 0:
						continue;
					case 128:
						break;
					}
					break;
				}
				if (i == mBlockSizeInBytes)
				{
					throw new CryptographicException();
				}
				break;
			}
			case PaddingMode.Zeros:
			{
				int j;
				for (j = 0; j < mBlockSizeInBytes; j++)
				{
					if (OutputBuffer[--OutputOffset] != 0)
					{
						OutputOffset++;
						break;
					}
				}
				if (j == 0)
				{
					throw new CryptographicException();
				}
				break;
			}
			}
		}
		mIsHomeBlock = true;
		if (OutputOffset < OutputBuffer.Length)
		{
			Array.Resize(ref OutputBuffer, OutputOffset);
		}
		return OutputBuffer;
	}

	public void Dispose()
	{
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}

	protected virtual void Dispose(bool disposing)
	{
		if (disposed)
		{
			return;
		}
		if (disposing)
		{
			if (mInnerIV != null)
			{
				Array.Clear(mInnerIV, 0, mInnerIV.Length);
			}
			if (mLiveIV != null)
			{
				Array.Clear(mLiveIV, 0, mLiveIV.Length);
			}
			if (mInputBuffer != null)
			{
				Array.Clear(mInputBuffer, 0, mInputBuffer.Length);
			}
			if (mOutputBuffer != null)
			{
				Array.Clear(mOutputBuffer, 0, mOutputBuffer.Length);
			}
		}
		disposed = true;
	}
}
